Completed
Push — master ( 91ed57...b3122f )
by MusikAnimal
02:16
created

editcounter.js ➔ ... ➔ $.done   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
nc 1
dl 0
loc 3
rs 10
nop 1
1
$(function () {
2
    // Don't do anything if this isn't a Edit Counter page.
3
    if ($("body.ec").length === 0) {
4
        return;
5
    }
6
7
    // Set up charts.
8
    $(".chart-wrapper").each(function () {
9
        var chartType = $(this).data("chart-type");
10
        if ( chartType === undefined ) {
11
            return false;
12
        }
13
        var data = $(this).data("chart-data");
14
        var labels = $(this).data("chart-labels");
15
        var $ctx = $("canvas", $(this));
16
17
        /** global: Chart */
18
        new Chart($ctx, {
0 ignored issues
show
Unused Code Best Practice introduced by
The object created with new Chart($ctx, {Identif...e))))),false,false)))}) is not used but discarded. Consider invoking another function instead of a constructor if you are doing this purely for side effects.
Loading history...
19
            type: chartType,
20
            data: {
21
                labels: labels,
22
                datasets: [ { data: data } ]
23
            }
24
        });
25
26
        return undefined;
27
    });
28
29
    loadLatestGlobal();
30
31
    // Set up namespace toggle chart.
32
    setupToggleTable(window.namespaceTotals, window.namespaceChart, null, function (newData) {
33
        var total = 0;
34
        Object.keys(newData).forEach(function (namespace) {
35
            total += parseInt(newData[namespace], 10);
36
        });
37
        var namespaceCount = Object.keys(newData).length;
38
        $('.namespaces--namespaces').text(
39
            namespaceCount.toLocaleString() + " " +
40
            $.i18n('num-namespaces', namespaceCount)
41
        );
42
        $('.namespaces--count').text(total.toLocaleString());
43
    });
44
});
45
46
/**
47
 * Load top edits HTML via AJAX, to not slow down the initial page load.
48
 * Only load if container is present, which is missing in subroutes, e.g. ec-namespacetotals, etc.
49
 * @return {Deferred} jQuery Deferred promise.
50
 */
51
// function loadTopEdits()
52
// {
53
//     var dfd = $.Deferred();
54
//     var $topEditsContainer = $("#topedits-container");
55
56
//     if ($topEditsContainer[0]) {
57
//         /** global: xtBaseUrl */
58
//         var url = xtBaseUrl + 'topedits/'
59
//             + $topEditsContainer.data('project') + '/'
60
//             + $topEditsContainer.data('username') + '/all?htmlonly=yes';
61
//         $.ajax({
62
//             url: url,
63
//             timeout: 30000
64
//         }).done(function (data) {
65
//             $topEditsContainer.replaceWith(data);
66
//         }).fail(function (_xhr, _status, message) {
67
//             $topEditsContainer.replaceWith(
68
//                 $.i18n('api-error', 'TopEdits API: <code>' + message + '</code>')
69
//             );
70
//         }).always(function () {
71
//             dfd.resolve();
72
//         });
73
//     }
74
75
//     return dfd;
76
// };
77
78
/**
79
 * Load recent global edits' HTML via AJAX, to not slow down the initial page load.
80
 * Only load if container is present, which is missing in subroutes, e.g. ec-namespacetotals, etc.
81
 * @return {Deferred} jQuery Deferred promise.
82
 */
83
function loadLatestGlobal()
84
{
85
    var dfd = $.Deferred();
86
    var $latestGlobalContainer = $("#latestglobal-container");
87
88
    if ($latestGlobalContainer[0]) {
89
        /** global: xtBaseUrl */
90
        var url = xtBaseUrl + 'ec-latestglobal/'
91
            + $latestGlobalContainer.data('project') + '/'
92
            + $latestGlobalContainer.data('username') + '?htmlonly=yes';
93
        $.ajax({
94
            url: url,
95
            timeout: 30000
96
        }).done(function (data) {
97
            $latestGlobalContainer.replaceWith(data);
98
        }).fail(function (_xhr, _status, message) {
99
            $latestGlobalContainer.replaceWith(
100
                $.i18n('api-error', 'Global contributions API: <code>' + message + '</code>')
101
            );
102
        }).always(function () {
103
            dfd.resolve();
104
        });
105
    }
106
107
    return dfd;
108
}
109
110
/**
111
 * Set up the monthcounts or yearcounts chart.
112
 * @param {String} id 'year' or 'month'.
113
 * @param {Array} datasets Datasets grouped by mainspace.
114
 * @param {Array} labels The bare labels for the y-axis (years or months).
115
 * @param {Number} maxTotal Maximum value of year/month totals.
116
 */
117
window.setupMonthYearChart = function (id, datasets, labels, maxTotal) {
118
    /**
119
     * Namespaces that have been excluded from view via clickable
120
     * labels above the chart.
121
     * @type {Array}
122
     */
123
    var excludedNamespaces = [];
124
125
    /**
126
     * Number of digits of the max month/year total. We want to keep this consistent
127
     * for aesthetic reasons, even if the updated totals are fewer digits in size.
128
     * @type {Number}
129
     */
130
    var maxDigits = maxTotal.toString().length;
131
132
    /** @type {Array} Labels for each namespace. */
133
    var namespaces = datasets.map(function (dataset) {
134
        return dataset.label;
135
    });
136
137
    /**
138
     * Build the labels for the y-axis of the year/monthcount charts,
139
     * which include the year/month and the total number of edits across
140
     * all namespaces in that year/month.
141
     */
142
    function getYAxisLabels()
143
    {
144
        var labelsAndTotals = {};
145
        datasets.forEach(function (namespace) {
146
            if (excludedNamespaces.indexOf(namespace.label) !== -1) {
147
                return;
148
            }
149
150
            namespace.data.forEach(function (count, index) {
151
                if (!labelsAndTotals[labels[index]]) {
152
                    labelsAndTotals[labels[index]] = 0;
153
                }
154
                labelsAndTotals[labels[index]] += count;
155
            });
156
        });
157
158
        // Format labels with totals next to them. This is a bit hacky,
159
        // but it works! We use tabs (\t) to make the labels/totals
160
        // for each namespace line up perfectly.
161
        // The caveat is that we can't localize the numbers because
162
        // the commas are not monospaced :(
163
        return Object.keys(labelsAndTotals).map(function (year) {
164
            var digitCount = labelsAndTotals[year].toString().length;
165
            var numTabs = (maxDigits - digitCount) * 2;
166
167
            // +5 for a bit of extra spacing.
168
            return year + Array(numTabs + 5).join("\t") +
169
                labelsAndTotals[year];
170
        });
171
    }
172
173
    window[id + 'countsChart'] = new Chart($('#' + id + 'counts-canvas'), {
174
        type: 'horizontalBar',
175
        data: {
176
            labels: getYAxisLabels(),
177
            datasets: datasets
178
        },
179
        options: {
180
            tooltips: {
181
                intersect: true,
182
                callbacks: {
183
                    label: function (tooltip) {
184
                        return tooltip.xLabel.toLocaleString();
185
                    },
186
                    title: function (tooltip) {
187
                        var yLabel = tooltip[0].yLabel.replace(/\t.*/, '');
188
                        return yLabel + ' - ' + namespaces[tooltip[0].datasetIndex];
189
                    }
190
                }
191
            },
192
            responsive: true,
193
            maintainAspectRatio: false,
194
            scales: {
195
                xAxes: [{
196
                    stacked: true,
197
                    ticks: {
198
                        beginAtZero: true,
199
                        callback: function (value) {
200
                            if (Math.floor(value) === value) {
201
                                return value.toLocaleString();
202
                            }
203
                        }
204
                    }
205
                }],
206
                yAxes: [{
207
                    stacked: true
208
                }]
209
            },
210
            legend: {
211
                // Happens when the user enables/disables a namespace via the
212
                // labels above the chart.
213
                onClick: function (e, legendItem) {
214
                    // Update totals, skipping over namespaces that have been excluded.
215
                    if (legendItem.hidden) {
216
                        excludedNamespaces = excludedNamespaces.filter(function (namespace) {
217
                            return namespace !== legendItem.text;
218
                        });
219
                    } else {
220
                        excludedNamespaces.push(legendItem.text);
221
                    }
222
223
                    // Update labels with the new totals.
224
                    window[id + 'countsChart'].config.data.labels = getYAxisLabels();
225
226
                    // Yield to default onClick event, which re-renders the chart.
227
                    Chart.defaults.global.legend.onClick.call(this, e, legendItem);
228
                }
229
            }
230
        }
231
    });
232
}
233